import { AccountSetAsfFlags, Client, Wallet as XrplWallet, xrpToDrops } from "xrpl";
import { evmAddressToXrplAccount } from "../util/address-derivation";
import { BridgeConfig } from "./index";
import { XChainBridge } from "xrpl/dist/npm/models/common";
import { Log, LogStatus, LogType } from "../../util/Logger";
import { BridgeChainProvider } from "./BridgeChainProvider";

export class XrpBridgeChainProvider implements BridgeChainProvider<XrplWallet> {
    toXrpAddress(address: string): string {
        if (!address.startsWith("0x")) return address;
        else return evmAddressToXrplAccount(address);
    }

    async setupSignerList(client: Client, bridgeDoorWallet: XrplWallet, witnesses: string[], claimThreshold: number) {
        const signerListSet = await client.autofill({
            TransactionType: "SignerListSet",
            Account: bridgeDoorWallet.address,
            SignerQuorum: claimThreshold,
            SignerEntries: witnesses.map((w) => ({
                SignerEntry: {
                    Account: w,
                    SignerWeight: 1,
                },
            })),
        });
        const signed = bridgeDoorWallet.sign(signerListSet);
        await client.submit(signed.tx_blob);
    }

    async disableMasterKey(client: Client, bridgeDoorWallet: XrplWallet) {
        const signerListSet = await client.autofill({
            TransactionType: "AccountSet",
            Account: bridgeDoorWallet.address,
            SetFlag: AccountSetAsfFlags.asfDisableMaster,
        });
        const signed = bridgeDoorWallet.sign(signerListSet);
        await client.submit(signed.tx_blob);
    }

    async createBridge(client: Client, wallet: XrplWallet, config: BridgeConfig, bridgeConfig: XChainBridge) {
        Log(LogType.Bridge, LogStatus.ToDo, "Creating bridge...");
        const createBridge = await client.autofill({
            TransactionType: "XChainCreateBridge",
            Account: wallet.address,
            XChainBridge: bridgeConfig,
            // MinAccountCreateAmount: xrpToDrops(config.minCreateAmount),
            SignatureReward: xrpToDrops(config.minRewardAmount),
        });

        // sign transaction
        const signed = wallet.sign(createBridge);
        await client.submit(signed.tx_blob);
        Log(LogType.Bridge, LogStatus.Done, "Bridge created");

        Log(LogType.Bridge, LogStatus.ToDo, "Disabling bridge master key...");
        await this.disableMasterKey(client, wallet);
        Log(LogType.Bridge, LogStatus.Done, `Bridge master key disabled`);
    }

    async fundAccount(client: Client, address: string): Promise<XrplWallet> {
        const { wallet } = await client.fundWallet({ address: address, classicAddress: address } as XrplWallet, {
            faucetHost: "sidechain-faucet.devnet.rippletest.net",
        });
        return wallet;
    }

    async createTrustLine(client: Client, wallet: XrplWallet, issuerAddress: string, currencyCode: string): Promise<void> {
        await client.submit(
            {
                TransactionType: "TrustSet",
                Account: wallet.address,
                LimitAmount: {
                    currency: currencyCode,
                    issuer: issuerAddress,
                    value: "100000000000000",
                },
            },
            {
                autofill: true,
                wallet: wallet,
            },
        );
    }
    async setupAccounts(
        client: Client,
        bridgeDoorWallet: XrplWallet,
        witnesses: string[],
        threshold: number,
        issuer: string,
        currencyCode: string,
    ): Promise<XrplWallet> {
        Log(LogType.Bridge, LogStatus.ToDo, "Funding witnesses...");
        for (const witness of witnesses) {
            Log(LogType.Bridge, LogStatus.Working, `Funding witness ${witness}...`);
            await this.fundAccount(client, witness);
        }
        Log(LogType.Bridge, LogStatus.Done, "Witnesses funded");

        Log(LogType.Bridge, LogStatus.ToDo, "Creating bridge account...");
        await this.fundAccount(client, bridgeDoorWallet.address);
        Log(LogType.Bridge, LogStatus.Done, `Bridge account created ${bridgeDoorWallet.address}`);

        Log(LogType.Bridge, LogStatus.ToDo, "Creating bridge account trust line...");
        await this.createTrustLine(client, bridgeDoorWallet, issuer, currencyCode);
        Log(LogType.Bridge, LogStatus.Done, `Bridge account trust line created`);

        Log(LogType.Bridge, LogStatus.ToDo, "Setting up bridge account signer list...");
        await this.setupSignerList(client, bridgeDoorWallet, witnesses, threshold);
        Log(LogType.Bridge, LogStatus.Done, `Bridge account signer list set up`);

        return bridgeDoorWallet;
    }

    createBridgeWallet(): XrplWallet {
        return XrplWallet.generate();
    }

    getLockingBridgeConfig(config: BridgeConfig, issuingBridgeAddress: string, bridgeWallet: XrplWallet): XChainBridge {
        return {
            LockingChainDoor: bridgeWallet.address,
            LockingChainIssue: {
                currency: config.lockingChain.tokenCode,
                issuer: this.toXrpAddress(config.lockingChain.tokenIssuer),
            },
            IssuingChainDoor: this.toXrpAddress(issuingBridgeAddress),
            IssuingChainIssue: {
                currency: config.lockingChain.tokenCode,
                issuer: this.toXrpAddress(issuingBridgeAddress),
            },
        };
    }

    getIssuingBridgeConfig(config: BridgeConfig, lockingBridgeAddress: string, bridgeWallet: XrplWallet): XChainBridge {
        return {
            LockingChainDoor: this.toXrpAddress(lockingBridgeAddress),
            LockingChainIssue: {
                currency: config.lockingChain.tokenCode,
                issuer: this.toXrpAddress(config.lockingChain.tokenIssuer),
            },
            IssuingChainDoor: bridgeWallet.address,
            IssuingChainIssue: {
                currency: config.lockingChain.tokenCode,
                issuer: bridgeWallet.address,
            },
        };
    }

    async createLockingChainBridge(
        config: BridgeConfig,
        issuingBridgeAddress: string,
        bridgeWallet: XrplWallet,
    ): Promise<{ address: string; config: XChainBridge }> {
        const client = new Client(config.lockingChain.url);
        await client.connect();

        const bridgeDoorWallet = await this.setupAccounts(
            client,
            bridgeWallet,
            config.lockingChain.witnesses,
            config.threshold,
            config.lockingChain.tokenIssuer,
            config.lockingChain.tokenCode,
        );

        const bridgeConfig = this.getLockingBridgeConfig(config, issuingBridgeAddress, bridgeDoorWallet);
        await this.createBridge(client, bridgeDoorWallet, config, bridgeConfig);
        return { address: bridgeDoorWallet.address, config: bridgeConfig };
    }

    async createIssuingChainBridge(
        config: BridgeConfig,
        lockingBridgeAddress: string,
        bridgeWallet: XrplWallet,
    ): Promise<{ address: string; config: XChainBridge }> {
        const client = new Client(config.issuingChain.url);
        await client.connect();

        const bridgeDoorWallet = await this.setupAccounts(
            client,
            bridgeWallet,
            config.issuingChain.witnesses,
            config.threshold,
            config.lockingChain.tokenIssuer,
            config.lockingChain.tokenCode,
        );

        const bridgeConfig = this.getLockingBridgeConfig(config, lockingBridgeAddress, bridgeDoorWallet);
        await this.createBridge(client, bridgeDoorWallet, config, bridgeConfig);

        return { address: bridgeDoorWallet.address, config: bridgeConfig };
    }
}
